package com.zys.el.user.util.time;

import cn.hutool.core.bean.BeanUtil;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.*;

@Data
@AllArgsConstructor
public class MyGa {

    // 种群的规模（0-100）
    private Integer popSize = 32;
    // 种群的变异概率
    private Double mutProb = 0.4;
    // 种群交叉概率
    private Double crossProb = 0.3;
    // 种群产生新物种的概率
    private Double newSpeciesProb = 0.1;
    // 精英种群的个数
    private Integer elite = 10;
    // 进化代数（100-500）
    private Integer maxIter = 100;
    // 种群集
    private List<List<Schedule>> population;
    // 时间片总数
    private Integer scheduleNum = 132;
    // 待安排任务数
    private Integer taskNum = 10;
    // 时间片时长
    public static Integer timeInterval = 5;
    // 原始总群
    public List<Schedule> originPop;

    public MyGa() {
    }


    /**
     * 演化
     *
     * @param originPopulation 原始种群
     */
    public void evolution(List<Schedule> originPopulation) {
        // 初始化种群
        init_population(originPopulation);
        for (int i = 0; i < this.maxIter; i++) {

            caleRate();

            mutate();

            crossover();

        }
    }

    /**
     * 变异
     */
    public void mutate() {
        Random random = new Random();

        // 遍历每个种群
        for (List<Schedule> schedules : this.population) {
            // 将时间片改为另一个事件，如果时间超出，则判断是否冲突，如果时间未超出，从事件集中找出小于剩余时间的最大值，且不冲突
            // 遍历种群中每个个体
            ArrayList<Schedule> timeDataCpy = new ArrayList<>(this.originPop);
            for (Schedule schedule : schedules) {
                // 删除已安排的事件
                timeDataCpy.removeIf(timeData -> timeData.getTaskId().equals(schedule.getTaskId()));
            }

            double isDeleteProb = this.mutProb;
            double mutProb = this.mutProb;

            // 碱基链太长调整变异概率
            /*int tasks = 4;
            while (schedules.size() > tasks) {
                tasks++;
                isDeleteProb *= (1 + this.mutProb * 0.4);
                mutProb *= (1 + this.mutProb * 0.2);
            }*/


            Iterator<Schedule> schIter = schedules.iterator();
            while (schIter.hasNext()) {
                Schedule schedule = schIter.next();
                double randomMutProb = Math.random();
                if (mutProb < randomMutProb) continue;

                // 变异
                double randomDelete = Math.random();
                if (timeDataCpy.isEmpty() && isDeleteProb > randomDelete) { // 删除某片碱基
                    timeDataCpy.add(schedule);
                    schIter.remove();

                } else if (!timeDataCpy.isEmpty() && isDeleteProb <= randomDelete) { // 变异某个碱基片
                    Schedule newSch = timeDataCpy.get(random.nextInt(timeDataCpy.size()));
                    schedule.setTaskId(newSch.getTaskId());

                    timeDataCpy.remove(newSch);
                    timeDataCpy.add(schedule);
                    // TODO 随机增加时间或减少时间

                }

                /*int idx = random.nextInt(timeDataCpy.size());
                Schedule s = timeDataCpy.get(idx); // 新事件

                schedule.setSlot(new ArrayList<>(s.getSlot()));
                schedule.setLenSlot(s.getLenSlot());
                schedule.setTime(s.getTime());

                // 判断时间是否冲突
                List<Integer> newSlot = schedule.getSlot();
                Iterator<Schedule> iter = schedules.iterator();
                while (iter.hasNext()) {
                    Schedule sc = iter.next();
                    List<Integer> sl = sc.getSlot();

                    Integer sEnd = sl.get(sl.size() - 1);
                    Integer sStart = sl.get(0);

                    Integer newStart = newSlot.get(0);
                    Integer newEnd = newSlot.get(newSlot.size() - 1);

                    if (sc.equals(schedule)) continue;

                    if (sEnd > newStart) {
                        ArrayList<Integer> list = new ArrayList<>();
                        if (sc.getLenSlot() > schedule.getLenSlot()) {
                            for (int i = 0; i < sEnd - newStart + 1; i++) {
                                list.add(sEnd - i);
                            }
                            // 重设基因
                            sl.removeAll(list);
                            sc.setLenSlot(sc.getLenSlot() - list.size());
                            sc.setTime(5 * sc.getLenSlot());
                        } else if (sc.getLenSlot() < schedule.getLenSlot()) {
                            for (int i = 0; i < sEnd - newStart + 1; i++) {
                                list.add(newStart + i);
                            }
                            // 重设基因
                            newSlot.removeAll(list);
                            schedule.setLenSlot(schedule.getLenSlot() - list.size());
                            schedule.setTime(5 * schedule.getLenSlot());
                        } else { // 各让一半
                            int t = (int) Math.ceil((double) (sEnd - newStart + 1) / 2);
                            for (int i = 0; i < t; i++) {
                                list.add(newStart + i);
                            }
                            newSlot.removeAll(list);
                            schedule.setLenSlot(schedule.getLenSlot() - list.size());
                            schedule.setTime(5 * schedule.getLenSlot());

                            list.clear();

                            for (int i = 0; i < t; i++) {
                                list.add(sEnd - i);
                            }
                            sl.removeAll(list);
                            sc.setLenSlot(sc.getLenSlot() - list.size());
                            sc.setTime(5 * sc.getLenSlot());
                        }

                    } else if (sStart < newEnd) {
                        ArrayList<Integer> list = new ArrayList<>();
                        if (sc.getLenSlot() > schedule.getLenSlot()) {
                            for (int i = 0; i < newEnd - sStart + 1; i++) {
                                list.add(sStart + i);
                            }
                            sl.removeAll(list);
                            sc.setLenSlot(sc.getLenSlot() - list.size());
                            sc.setTime(5 * sc.getLenSlot());
                        } else if (sc.getLenSlot() < schedule.getLenSlot()) {
                            for (int i = 0; i < newEnd - sStart + 1; i++) {
                                list.add(newEnd - i);
                            }
                            newSlot.removeAll(list);
                            schedule.setLenSlot(schedule.getLenSlot() - list.size());
                            schedule.setTime(5 * schedule.getLenSlot());
                        } else { // 各让一半
                            int t = (int) Math.ceil((double) (newEnd - sStart + 1) / 2);
                            for (int i = 0; i < t; i++) {
                                list.add(newEnd - i);
                            }
                            newSlot.removeAll(list);
                            schedule.setLenSlot(schedule.getLenSlot() - list.size());
                            schedule.setTime(5 * schedule.getLenSlot());

                            list.clear();

                            for (int i = 0; i < t; i++) {
                                list.add(sStart + i);
                            }
                            sl.removeAll(list);
                            sc.setLenSlot(sc.getLenSlot() - list.size());
                            sc.setTime(5 * sc.getLenSlot());
                        }
                    } else if (newStart < sStart && newEnd > sEnd) { // 把被冲突的吃掉
                        iter.remove();
                    }
                }*/
            }
        }

    }

    /**
     * 交叉
     */
    public void crossover() {
        Random random = new Random();

        Iterator<List<Schedule>> iter = this.population.iterator();
        while (iter.hasNext()) {
            List<Schedule> toCrossScList = iter.next();
            double rate = Math.random();
            if (rate < this.crossProb) {
                // TODO 随机选取其他任意一个种群中，随机一个已存在的种群个体进行交叉，交叉时间片，如果有冲突，则交换，如果不用交换，则执行解决冲突

                // 如果不存在此种群，则任选一个此种群中不存在的个体进行交叉，交叉事件和时间片
                int idx = random.nextInt(this.population.size());
                List<Schedule> sc = this.population.get(idx);
                Schedule toCrossSc = toCrossScList.get(random.nextInt(toCrossScList.size() - 1));
                List<Integer> toCrossScSlot = toCrossSc.getSlot();

                Schedule newSc = sc.get(random.nextInt(sc.size() - 1));

                Iterator<Schedule> toCrossIter = toCrossScList.iterator();

                toCrossSc.setTaskId(newSc.getTaskId());
                toCrossSc.setTime(newSc.getTime());
                toCrossSc.setLenSlot(newSc.getLenSlot());

            }
        }
    }


    /**
     * 初始化
     *
     * @param originPopulation 原始种群
     */
    public void init_population(List<Schedule> originPopulation) {
        this.population = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < this.popSize; i++) {
            List<Schedule> entity = new ArrayList<>();
            for (int j = 0; j < taskNum; j++) {
                Schedule tmp = originPopulation.get(j);
                tmp.random_init(this.scheduleNum);

                // 添加进种群
                Schedule schedule = new Schedule(tmp.getTaskId(), tmp.getSlot(), tmp.getTime(), tmp.getLenSlot());
                entity.add(schedule);
                // 有冲突则把刚才的移除掉
                if (Schedule.hasConflict(entity)) {
                    entity.remove(schedule);
                }
            }
            this.population.add(entity);
        }
    }


    /**
     * 计算适应度
     */
    public void caleRate() {
        HashMap<List<Schedule>, Double> map = new HashMap<>();
        // 计算冲突，有冲突，适应度为0
        for (List<Schedule> schedules : this.population) {
            map.put(schedules, Schedule.hasConflict(schedules) ? 1 / 5D : 0);
        }

        // 计算跨越休息时间的事件适应度
        HashSet<Integer> set = new HashSet<>();
        for (List<Schedule> schedules : this.population) {
            int unqualifiedSpanning = 0;
            int submit = 0;
            set.clear();
            for (Schedule schedule : schedules) {
                List<Integer> slot = schedule.getSlot();
                set.addAll(slot);
                int n = slot.size();
                Integer start = slot.get(0);
                Integer end = slot.get(n - 1);
                if ((end > 47 && start < 47) || (end > 106 && start < 107)) {
                    if (end - 48 < 47 - start || end - 107 < 106 - start) {
                        if (end - 48 < 4 || end - 107 < 4) {
                            unqualifiedSpanning++;
                        } else {
                            submit++;
                        }
                    } else {
                        submit++;
                    }
                }
            }
            Double rate = map.get(schedules) + Math.pow(0.2, unqualifiedSpanning) // 跨越休息时间
                    + submit * 0.2 // 非跨越休息时间
                    + (double) set.size() / this.taskNum * 2; // 安排全天饱满度
            map.put(schedules, rate);
        }
        // 按适应度排序
        ArrayList<Map.Entry<List<Schedule>, Double>> entries = new ArrayList<>(map.entrySet());
        entries.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));

        List<List<Schedule>> newPopulation = new ArrayList<>();
        // 赋值回种群集
        int talentNum = this.popSize / 4;
        for (int i = 0; i < talentNum; i++) {
            List<Schedule> schedules = entries.get(i).getKey();
            newPopulation.add(schedules);
        }
        this.population = newPopulation;

        // 轮盘赌 种群规模上限-精英数 次
        /*int roundNum = this.popSize - talentNum;
        for (int i = 0; i < roundNum; i++) {
            double rate = Math.random();
            if (rate < this.newSpeciesProb) {
                Random random = new Random();
                List<Schedule> schedules = this.population.get(random.nextInt(this.population.size() - 1));

                // 添加新基因位

                // 判断时长是否大于30分钟
                Schedule maxLenSch = schedules.get(0);
                for (int j = 1; j < schedules.size(); j++) {
                    if (maxLenSch.getLenSlot() > schedules.get(j).getLenSlot()) {
                        maxLenSch = schedules.get(j);
                    }
                }
                int lenSlot = maxLenSch.getLenSlot();
                if (lenSlot > 6) { // 大于30分钟才产生新基因位
                    // 获取此种群没有的新基因
                    List<Schedule> notContainSchedules = this.getNotContainSchedule(schedules);
                    // 新基因位
                    int idx = random.nextInt(notContainSchedules.size());

                    List<Integer> maxLenSchSlot = maxLenSch.getSlot();
                    Schedule newSch = notContainSchedules.get(idx);
                    int newLenSlot = random.nextInt(lenSlot - 3) + 1;
                    newSch.setLenSlot(newLenSlot);

                    // 新基因占位
                    ArrayList<Integer> newSlot = new ArrayList<>();
                    Integer slotPlace;

                    int startOrEnd = random.nextInt(2);
                    if (startOrEnd == 0) { // 从当前基因位的开始时间往后截取
                        // 旧基因位位置
                        slotPlace = maxLenSchSlot.get(0);
                        // 新基因位
                        for (int j = 0; j < newLenSlot; j++)
                            newSlot.add(slotPlace + j);

                    } else { // 从当前基因位的结束时间往前截取
                        // 旧基因位位置
                        slotPlace = maxLenSchSlot.get(maxLenSchSlot.size() - 1);
                        // 新基因位
                        for (int j = 0; j < newLenSlot; j++)
                            newSlot.add(slotPlace - j);

                    }
                    // 添加新基因占位
                    newSch.setSlot(newSlot);
                    newSch.setLenSlot(newSlot.size());
                    newSch.setTime(5 * newSch.getLenSlot());
                    // 移除旧基因占的位置
                    maxLenSchSlot.removeAll(newSlot);
                    maxLenSch.setLenSlot(maxLenSch.getLenSlot() - newSch.getLenSlot());
                    maxLenSch.setTime(maxLenSch.getTime() - newSch.getTime());
                }

            }
        }*/
    }

    /**
     * 获取未安排的任务
     *
     * @param schedule 已安排的任务种群
     * @return
     */
    public List<Schedule> getNotContainSchedule(List<Schedule> schedule) {
        ArrayList<Schedule> res = new ArrayList<>();
        HashMap<String, Integer> map = new HashMap<>();
        HashMap<String, Schedule> oriMap = new HashMap<>();
        // 初始化map
        this.originPop.forEach(o -> map.put(o.getTaskId(), 0));
        this.originPop.forEach(s -> oriMap.put(s.getTaskId(), s));

//        System.out.println(map);
        // 打点计数
        schedule.forEach(s -> map.put(s.getTaskId(), map.get(s.getTaskId()) + 1));

        ArrayList<Map.Entry<String, Integer>> entries = new ArrayList<>(map.entrySet());
        entries.sort(Map.Entry.comparingByValue());

        for (Map.Entry<String, Integer> entry : entries) {
            if (entry.getValue() == 0) {
                Schedule s = oriMap.get(entry.getKey());
                res.add(BeanUtil.copyProperties(s, Schedule.class));
            } else {
                break;
            }
        }

        return res;
    }

/*
    public void printEveryProgeny(Map<List<Schedule>, Integer> lists) {
        HashMap<Integer, Integer[][][]> room = new HashMap<>();

        // 初始化map
        for (int i = 0; i < this.popSize; i++) {
            Integer[][][] k = new Integer[1][4][5];
            for (Integer[][] j : k) {
                for (Integer[] t : j) {
                    Arrays.fill(t, 0);
                }

            }
            room.put(i, k);
        }
        // 转换为三维数组数据
        int ii = 0;
        for (Map.Entry<List<Schedule>, Integer> entry : lists.entrySet()) {
            List<Schedule> key = entry.getKey();
            ii++;
            for (Schedule schedule : key) {
                room.get(ii)[Integer.parseInt(schedule.getClassroomId())][schedule.getSlot() - 1][schedule.getWeekday() - 1] = 1;
            }
        }

        // 打印
        for (Map.Entry<Integer, Integer[][][]> entry : room.entrySet()) {
            Integer[][][] value = entry.getValue();
            System.out.println("种群" + entry.getKey());
            System.out.println("*********---------**********");
            for (int i = 0; i < value.length; i++) {
                System.out.println("教室" + i);
                Integer[][] v = value[i];
                for (Integer[] schedules : v) {
                    for (Integer day : schedules) {
                        System.out.print(day + " ");
                    }
                    System.out.println();
                }
            }
        }
    }
*/

/*
    public void printEveryProgeny(List<Schedule> list) {
        System.out.println("---------------==================------------");
        HashMap<String, Integer[][]> room = new HashMap<>();

        room.put("0", new Integer[4][5]);
//        room.put("1", new Integer[4][5]);
//        room.put("2", new Integer[4][5]);
        room.forEach((k, v) -> {
            for (Integer[] i : v) {
                Arrays.fill(i, 0);
            }
        });

        for (Schedule schedule : list) {
            room.get(schedule.getClassroomId())[schedule.getSlot() - 1][schedule.getWeekday() - 1] = 1;
        }

        for (Map.Entry<String, Integer[][]> entry : room.entrySet()) {
            Integer[][] value = entry.getValue();
            System.out.println("教室" + entry.getKey());
            for (Integer[] schedules : value) {
                for (Integer day : schedules) {
                    System.out.print(day + " ");
                }
                System.out.println();
            }
        }
    }
*/

    public List<List<Schedule>> result() {
        MyGa myGa = new MyGa();
        ArrayList<Schedule> schedules = new ArrayList<>();
        ArrayList<String> roomRange = new ArrayList<>();
        Random random = new Random();
        int originSlot = 0;
        for (int i = 0; i < myGa.taskNum; i++) {
            int time = random.nextInt(50) + 20;
            schedules.add(new Schedule("Task_" + i, time));
            originSlot += time / 5;
        }

//        System.out.println("初始饱满度：" + originSlot / myGa.taskNum * 10 + "%");

        myGa.originPop = schedules;
        myGa.evolution(schedules);
//        System.out.println("---------------========---------------");
        return myGa.population;
    }

//    public static void main(String[] args) {
//        MyGa myGa = new MyGa();
//        ArrayList<Schedule> schedules = new ArrayList<>();
//        ArrayList<String> roomRange = new ArrayList<>();
//        Random random = new Random();
//        int originSlot = 0;
//        for (int i = 0; i < myGa.taskNum; i++) {
//            int time = random.nextInt(50) + 20;
//            schedules.add(new Schedule("Task_" + i, time));
//            originSlot += time / 5;
//        }
//
//        System.out.println("初始饱满度：" + originSlot / myGa.taskNum * 10 + "%");
//
//        myGa.originPop = schedules;
//        myGa.evolution(schedules);
//        System.out.println("---------------========---------------");
//        for (List<Schedule> scheduleList : myGa.population) {
//            System.out.println(scheduleList);
//            System.out.println("个数：" + scheduleList.size());
//            AtomicInteger allSlot = new AtomicInteger();
//            scheduleList.forEach(s -> allSlot.addAndGet(s.getSlot().size()));
//            System.out.println("安排饱满度：" + (double) allSlot.get() / myGa.taskNum * 10 + "%");
//        }
//        System.out.println("---------------========---------------");
//
//    }
}